import string
import time
import pygame
from word_wrap import wrap_text

class PyGameInputHandler:
    def __init__(self):
        self.held_keys = set()
        self.last_press_time = {}  # Track last press time per key
        self.input_delay = 0.2  # Delay in seconds for held keys
        self.key_mapping = {
            pygame.K_a: "a", pygame.K_b: "b", pygame.K_x: "a", pygame.K_y: "b",
            pygame.K_m: "m", pygame.K_n: "n",
            pygame.K_UP: "up", pygame.K_DOWN: "down", pygame.K_LEFT: "left", pygame.K_RIGHT: "right"
        }

    def process_input(self, game_state):
        current_time = time.time()

        # Handle events
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                exit()
            elif event.type == pygame.KEYDOWN:
                if event.key in self.key_mapping:
                    action = self.key_mapping[event.key]
                    if action not in self.held_keys:
                        # Immediate response on tap
                        game_state.update_state(action)
                        self.last_press_time[action] = current_time  # Set press time
                    self.held_keys.add(action)  # Mark key as held
            elif event.type == pygame.KEYUP:
                if event.key in self.key_mapping:
                    action = self.key_mapping[event.key]
                    self.held_keys.discard(action)  # Key is no longer held
                    if action in self.last_press_time:
                        del self.last_press_time[action]  # Reset timing

        # Process held keys (apply delay per key)
        for button in self.held_keys:
            if button in self.last_press_time and current_time - self.last_press_time[button] >= self.input_delay:
                game_state.update_state(button)
                self.last_press_time[button] = current_time  # Update timestamp

        # Continuously save the game
        game_state.data_manager.save_game(game_state)

class PyGameOutputHandler:
    def __init__(self, screen, font_info, screen_info):
        self.screen = screen
        self.font = font_info['font']
        self.max_width = font_info['wrap_width']
        self.max_height = font_info['wrap_height']
        self.font_info = font_info
        self.screen_info = screen_info
        self.theme_id = None
        self.theme = None
        self.background = None

    def lookup_theme_color(self, text_type):
        if text_type == 'default-text':
            return (self.theme['default-text']['r'],
                    self.theme['default-text']['g'],
                    self.theme['default-text']['b'])
        if text_type == 'error-text':
            return (self.theme['error-text']['r'],
                    self.theme['error-text']['g'],
                    self.theme['error-text']['b'])
        if text_type == 'puzzle-text':
            return (self.theme['puzzle-text']['r'],
                    self.theme['puzzle-text']['g'],
                    self.theme['puzzle-text']['b'])
        else:
            return

    def calculate_menu_offset(self, total_items, cursor_position, menu_height):
        if total_items <= menu_height:
            return 0

        half_height = menu_height // 2

        if cursor_position <= half_height:
            return 0

        if total_items - cursor_position <= half_height:
            return total_items - menu_height

        return cursor_position - half_height

    def render(self, game_state):
        self.screen.fill((0, 0, 0))  # Black background
        y_offset_count = 0
        x_offset_count = 0
        font_height = self.font_info['font_height']
        font_width = self.font_info['font_width']
        game_surface = pygame.Surface((self.screen_info['game_width'], self.screen_info['game_height']))

        # Check if we need to pre-load a new background image
        if (game_state.user_preferences.visual_theme != self.theme_id):
            self.theme_id = game_state.user_preferences.visual_theme
            self.theme = game_state.data_manager.get_theme(self.theme_id)
            background_file_path = "data/backgrounds/" + self.theme['background-path']
            self.background = pygame.image.load(background_file_path)

        game_surface.blit(self.background, (0, 0))

        if game_state.screen == 'exit_to_os':
            return
        elif len(game_state.dialog_list) > 0:
            for line_text in wrap_text(game_state.dialog_list[0].current_page(), self.max_width):
                text_surface = self.font.render(line_text, True, self.lookup_theme_color('default-text'))
                game_surface.blit(text_surface, (20, 20 + font_height * y_offset_count * 2))
                y_offset_count += 1
        elif game_state.screen == 'menu':
            # Render Settings Menu
            menu_height = self.font_info['menu_height']
            begin_menu_i = self.calculate_menu_offset(len(game_state.menu.menu_options), game_state.menu.selected_option, menu_height)

            for i, option in enumerate(game_state.menu.menu_options[begin_menu_i:begin_menu_i + menu_height]):
                if (i + begin_menu_i) == game_state.menu.selected_option:
                    cursor_surface = self.font.render('->', True, self.lookup_theme_color('default-text'))
                    game_surface.blit(cursor_surface, (10, 40 + i * font_height * 2))

                if 'option_name' in option:
                    menu_surface = self.font.render(option['option_name'], True, self.lookup_theme_color('default-text'))
                    game_surface.blit(menu_surface, (40, 40 + i * font_height * 2))
        else:
            puzzle_page_info = game_state.get_puzzle_output_pages()
            pages = puzzle_page_info['pages']
            page_index = puzzle_page_info['cursor_page']
            max_pages = len(pages)
            if len(pages) > 0:
                page = pages[page_index]
            else:
                page = []
            for i in page:
                is_red_mark = i['is_red_mark']
                is_cursor = i['is_cursor']
                x_offset_count = i['x_offset']
                y_offset_count = i['y_offset']
                puzzle_letter = i['puzzle_letter']
                guess_letter = i['guess_letter']

                if is_red_mark:
                    guess_surface = self.font.render(guess_letter, True, self.lookup_theme_color('error-text'))
                else:
                    guess_surface = self.font.render(guess_letter, True, self.lookup_theme_color('default-text'))
                puzzle_surface = self.font.render(puzzle_letter, True, self.lookup_theme_color('puzzle-text'))

                game_surface.blit(guess_surface, (20 + font_width * x_offset_count, 20 + font_height * y_offset_count * 5))
                game_surface.blit(puzzle_surface, (20 + font_width * x_offset_count, 40 + font_height * y_offset_count * 5))

                if is_cursor:
                    cursor_surface = self.font.render('^', True, self.lookup_theme_color('default-text'))
                    game_surface.blit(cursor_surface, (20 + font_width * x_offset_count, 60 + font_height * y_offset_count * 5))

                x_offset_count += 1

            page_display_info = str(page_index + 1) + '/' + str(max_pages)
            page_display_surface = self.font.render(page_display_info, True, self.lookup_theme_color('default-text'))
            game_surface.blit(page_display_surface, (self.screen_info['game_width'] - 65, self.screen_info['game_height'] - 45))

        # Create a scaled version of the game surface
        scaled_game_surface = pygame.transform.scale(game_surface, (self.screen_info['new_width'], self.screen_info['new_height']))

        # Blit the scaled game surface to the screen with black bars
        self.screen.blit(scaled_game_surface, (self.screen_info['x_offset'], self.screen_info['y_offset']))

        pygame.display.flip()  # Refresh screen
